SPB Git

spb/airiskindex Public

The most methodologically rigorous, fully transparent AI job-exposure index.

TypeScript 88% Python 6.1% SQL 2.7% CSS 1.2% JavaScript 0.9% Shell 0.8%
2.5 KB · 80 lines typescript
Raw Blame History
1//  File:    route.ts2//  Path:    apps/web/app/api/v1/occupations/[code]/route.ts3//  Project: AI Risk Index — airiskindex.io4//  Author:  Simon-Pierre Boucher5//  Contact: contact@spboucher.ai6//  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.7//8//  Description: Public API: full score breakdown for one occupation.910import { prisma } from "@airiskindex/db";11import { INDEX_VERSION } from "@airiskindex/scoring";12import type { NextRequest } from "next/server";1314export const dynamic = "force-dynamic";1516export async function GET(17  _request: NextRequest,18  { params }: { params: { code: string } },19): Promise<Response> {20  try {21    const occupation = await prisma.occupation.findUnique({22      where: { code: params.code },23      include: {24        tasks: {25          select: { id: true, statement: true, importance: true },26          orderBy: { id: "asc" },27        },28      },29    });30    if (!occupation) {31      return Response.json({ error: "not_found" }, { status: 404 });32    }3334    // Latest run's scores; historical runs stay queryable (CLAUDE.md §9).35    const latest = await prisma.occupationScore.findFirst({36      where: { occupationCode: occupation.code },37      orderBy: { run: { createdAt: "desc" } },38      include: { run: true },39    });4041    return Response.json({42      index_version: latest?.run.indexVersion ?? INDEX_VERSION,43      occupation: {44        code: occupation.code,45        title: occupation.title,46        description: occupation.description,47        esco_uri: occupation.escoUri,48        rome_code: occupation.romeCode,49        median_wage_cents: occupation.medianWageCents,50        wage_currency: occupation.wageCurrency,51      },52      scores: latest53        ? {54            run_id: latest.runId,55            computed_at: latest.run.createdAt,56            substitution: {57              low: latest.substitutionLow,58              score: latest.substitution,59              high: latest.substitutionHigh,60            },61            exposure: {62              low: latest.exposureLow,63              score: latest.exposure,64              high: latest.exposureHigh,65            },66            augmentation: {67              low: latest.augmentationLow,68              score: latest.augmentation,69              high: latest.augmentationHigh,70            },71            highly_exposed_task_share: latest.highlyExposedTaskShare,72          }73        : null,74      tasks: occupation.tasks,75    });76  } catch {77    return Response.json({ error: "database_unavailable" }, { status: 503 });78  }79}80